#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <ctime>
#include <stdlib.h>
#include <string.h>

#define REMOTE_ADDR_LEN sizeof(struct sockaddr_in)
#define RESPONSE_LEN 1500
#define MSG_LEN 32

struct ICMPheader
{
	unsigned char	byType;
	unsigned char	byCode;
	unsigned short	nChecksum;
	unsigned short	nId;
	unsigned short	nSequence;
};

struct IPheader
{
    unsigned char	byVerLen;        
    unsigned char	byTos;       
    unsigned short	nTotalLength;   
    unsigned short	nId;            
    unsigned short	nOffset;        
    unsigned char	byTtl;          
    unsigned char	byProtocol;     
    unsigned short	nChecksum;      
    unsigned int	nSrcAddr;       
    unsigned int	nDestAddr;      
};

#define IP_HEADER_LEN sizeof(struct IPheader)
#define ICMP_HEADER_LEN sizeof(struct ICMPheader)
#define BUF_LEN ICMP_HEADER_LEN+MSG_LEN

using namespace std;

unsigned short CalcChecksum (char *pBuffer, int nLen);
bool ValidateChecksum (char *pBuffer, int nLen);

int main(int argc, char* argv[])
{
	if (argc < 2)
	{
		cout <<"mytraceroute: missing destination hostname"<< endl;
		return 0;
	}
	char *pSendBuffer = NULL, *destHostName = NULL;
	destHostName = argv [1];
	hostent *host = gethostbyname(destHostName);
    if(host == NULL) { perror("wrong host name");}
    
    struct sockaddr_in destAddr, remoteAddr;
    destAddr.sin_addr.s_addr = ((in_addr*)host->h_addr)->s_addr;
    printf("This is finded ip %s\n", inet_ntoa(*(struct in_addr*)host->h_addr));
	destAddr.sin_family = AF_INET;
	destAddr.sin_port = 30001;
    
    int sock = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (sock < 0 ) {
		perror("socket");
		return errno;
	}
    
    struct ICMPheader sendHdr;
    sendHdr.nId = htons (1);	
	sendHdr.byCode = 0;	
	sendHdr.byType = 8;	
    
    int nSequence = 0;
	int nMaxRetries = 3;
	int nTTL = 2;	
			
	time_t timeSend, timeRecv;
	fd_set fdRead;
	int nTimeOut = 60;
	struct timeval timeInterval = {nTimeOut, 0};
	int nHopCount = 30;
	
	cout << "Tracing route to " << destHostName << " over a maximum of " << nHopCount << " hops." << endl << endl;
	
	int nHopsTraversed = 0;
	int nResult = 0;
	while (nHopsTraversed < nHopCount &&  destAddr.sin_addr.s_addr != remoteAddr.sin_addr.s_addr) {
		cout << " hops traversed  " << nHopsTraversed + 1;
		if (setsockopt (sock, IPPROTO_IP, IP_TTL, (char *)&nTTL, sizeof (nTTL)) != 0) {
			perror("An error occured in setsockopt operation");
			return errno;
		}
		pSendBuffer = new char [BUF_LEN];
		sendHdr.nSequence = htons (nSequence++);
		sendHdr.nChecksum = 0;

		memcpy(pSendBuffer, &sendHdr, ICMP_HEADER_LEN);
		memset (pSendBuffer + ICMP_HEADER_LEN, 'x', MSG_LEN);
		sendHdr.nChecksum = htons (CalcChecksum (pSendBuffer, BUF_LEN));	
		
		int nRetries = 0;
		struct IPheader ipHdr;
		bool bGotAResponse = false;	
		while (nRetries < nMaxRetries) {
			if ((nResult = sendto (sock, pSendBuffer, BUF_LEN, 0, 
				(struct sockaddr*)&destAddr, REMOTE_ADDR_LEN)) < 0) {
				perror("An error occured in sendto operation");
				return errno;
			}
			printf("\n\tsend %d", nResult);
			timeSend = time(0);
			FD_ZERO (&fdRead);
			FD_SET (sock, &fdRead);
			if ((nResult = select (sock+1, &fdRead, NULL, NULL, &timeInterval)) <= 0) {
				perror("An error occured in select operation");
				return errno;
			}
			if ((nResult > 0) && FD_ISSET(sock, &fdRead)) {
				char *pRecvBuffer = new char[RESPONSE_LEN];
				socklen_t remoteAddrLen = REMOTE_ADDR_LEN;
				if ((nResult = recvfrom(sock, pRecvBuffer, RESPONSE_LEN, 0, (struct sockaddr*)&remoteAddr, &remoteAddrLen)) <= 0){
					printf("An error occured in recvfrom operation: receive returns %d\n", nResult); 
					return errno;
				}	
				cout << "\n\t retry " << nRetries << " receive " << nResult;
				timeRecv = time(0);
				bGotAResponse = true;
				struct ICMPheader recvHdr;
				char *pICMPbuffer = pRecvBuffer + IP_HEADER_LEN;
				int nICMPMsgLen = nResult - IP_HEADER_LEN;
				memcpy(&recvHdr, pICMPbuffer, ICMP_HEADER_LEN);
				memcpy(&ipHdr, pRecvBuffer, IP_HEADER_LEN);
				recvHdr.nChecksum = ntohs (recvHdr.nChecksum);
				if (ValidateChecksum (pICMPbuffer, nICMPMsgLen)) {
					double nSec = difftime(timeRecv, timeSend);
					if (nSec <= 0) nSec = 0;
					cout << '\t' << nSec << " sec";
				} else {
					cout << "\t!";					
				}
				delete []pRecvBuffer;
			} else {//Request timed out so we try again
				cout << "\t*";
			}
			++nRetries;
		}
		if (bGotAResponse == false) {
			cout << "\tRequest timed out.";
		} else {
			struct in_addr in;
			in.s_addr = ipHdr.nSrcAddr;
			char *nodeIpStr = inet_ntoa(in);
			struct hostent *node = gethostbyaddr((char*)&in.s_addr, sizeof(in.s_addr), AF_INET );
			if (node == NULL) {
				cout << "\tError resolving host name" << "[" << nodeIpStr << "]"<< endl;
			} else {
				cout << '\t' << node->h_name << " [" << nodeIpStr << "]";
			}
		}
		cout << endl << '\r';
		++nHopsTraversed;
		++nTTL;
		delete []pSendBuffer;
	}
	cout << endl << "Trace complete." << endl;

	return 0;
}

unsigned int calcNSum (char *pBuffer, int nLen) {
	unsigned short nWord;
	unsigned int nSum = 0;
	int i;
    for (i = 0; i < nLen; i = i + 2)
	{
		nWord =((pBuffer [i] << 8)& 0xFF00) + (pBuffer [i + 1] & 0xFF);
		nSum = nSum + (unsigned int)nWord;	
	}
	while (nSum >> 16)
	{
		nSum = (nSum & 0xFFFF) + (nSum >> 16);
	}
	return nSum;
}

unsigned short CalcChecksum (char *pBuffer, int nLen)
{
	unsigned int nSum = calcNSum(pBuffer, nLen);
	nSum = ~nSum;
	return ((unsigned short) nSum);
}

bool ValidateChecksum (char *pBuffer, int nLen)
{
	unsigned int nSum = calcNSum(pBuffer, nLen);
	return ((unsigned short)nSum == 0xFFFF);
}
